import queue
import re
import sys
import threading
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
import os
import time
import numpy as np
import pandas as pd
from docx import Document
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml import OxmlElement
from docx.shared import RGBColor, Pt, Cm
from docx.oxml.ns import qn
from matplotlib import pyplot as plt
from pymongo.mongo_client import MongoClient
import win32com.client as win32

# 样式配置类
class StyleConfig:
    COLORS = {
        "primary": "#1a73e8",  # 主色调，更明亮的蓝色
        "secondary": "#5f6368",  # 次要色，深灰
        "success": "#34a853",  # 成功色，绿色
        "danger": "#ea4335",  # 危险色，红色
        "warning": "#fbbc05",  # 警告色，黄色
        "info": "#4285f4",  # 信息色，蓝色
        "light": "#f8f9fa",  # 浅色背景
        "dark": "#3c4043"  # 深色文本
    }

    # 日志级别对照表
    LOG_LEVELS = {
        "DEBUG": "#666666",  # 灰色
        "INFO": "#1967d2",  # 蓝色
        "SUCCESS": "#00c250",  # 绿色
        "WARNING": "#ff9800",  # 橙色
        "ERROR": "#d50000"  # 深红色
    }

    FONTS = {
        "title": ("微软雅黑", 24, "bold"),
        "subtitle": ("微软雅黑", 12),
        "button": ("微软雅黑", 12),
        "treeview": ("宋体", 10),
        "log": ("Consolas", 10)
    }

    LOGIN_UI = {
        "window": "300x350",
        "treeview": {"width": 200, "height": 15},
        "button_pad": 15
    }

    DIMENSIONS = {
        "window": "1000x700",
        "treeview": {"width": 200, "height": 15},
        "button_pad": 15
    }

# 公共配置类
class Config:
    connection_string = ("mongodb+srv://14242993:CrM839bZmOfOb4yQ@"
                         "myinfo.qhpkw.mongodb.net/?retryWrites=true&w=majority&appName=myinfo")
    SUBJECTS = ['语文', '数学', '英语', '物理', '化学', '生物', '政治', '历史', '地理']  # 学科列表
    STATES = ['已掌握', '已攻克', '待拔高', '待攻克', '待补弱', '待练习']  # 知识点状态列表
    COLORS = ['#47cdc2',   # 青绿色
              '#3167ff',   # 天蓝色
              '#feb624',   # 橙黄色
              '#ff83a7',   # 粉红色
              '#c5323c',   # 深红色
              '#d7e2ff'    # 淡蓝色（灰色）
              ]  # 图表颜色列表
    LABELS = {
        'title': '智慧课堂学情报告',
        'para1': '{grade}{student}同学感谢您使用 OK 智慧课堂，收获满满的高一已经过去，在'
                 '浩瀚的智慧课堂系统数据库里留下了你辛勤付出的足迹，根据你的数字化学习轨迹，我们的数'
                 '据分析团队为你制作了专属于你的学情诊断分析报告，希望通过这份数据报告，能让你清楚自'
                 '己的知识薄弱点、准确修正自己的不足，为自己下学期的学习打好各方面基础。也希望通过这'
                 '份数据报能让家长更深入地了解孩子学情、减轻家长的辅导负担、掌握找到更好的家校共育方'
                 '法、能通过监督陪伴更有效提升孩子的学习力。本文通过数据分析清晰地向家长和学生展示了'
                 '学生的各课的学习情况。',
        'para2': '本次学情数据，它包括了每个学生在预习、上课、测评、作业、自主学'
                 '习等各个场景的学习情况，这些数据真实地反映出你的科目偏好、学习情况、自主学习能力等'
                 '因素。希望你仔细阅读并认真分析报告内容，学会总结并通过数据进行思考，找到最适合自己'
                 '学习的方式方法。',
        'para3': '制表说明：（请学生和家长认真阅读）',
        'para4': '1. 数据时间范围：2024-09-01至{end_time}；本期数据'
                 '维度为学生在学生机上产生数据，全科学科包含知识点维度；',
        'para5': '2. 本文不分析成绩，仅通过学生自己产生的智慧课堂学情数据，结合'
                 '大数据分析，对学生通过智慧课堂产生的的学习力进行客观评估；\n3. 篇幅有限，如有疑问'
                 '或需要更加详细的数据，请反馈给班主任老师和运营老师。',
        'para6': '以下是全学科掌握的情况：'
    }  # 固定内容标签
    tips_content = [
        '温馨提示！！！',
        '无数据的学科仅表示未进行该科知识点习题训练，有数据的知识点层级说明如下：',
        '已掌握：新知识点连续做对了10道题以上； | 已攻克：该知识点错题全做对以后，又连续做对了10道以上变式题；',
        '待拔高：该知识点错题重做全做对了； | 待攻克：该知识点错了1道题；',
        '待补弱：该知识点连续错了2道题以上； | 待练习：开始学习该知识点，但未达到10道题；'
    ]
    help_content = """使用说明：
                    1. 点击【选择路径】按钮指定包含学生数据的文件夹
                    2. 点击【生成报告】开始自动处理数据并生成Word文档
                    3. 查看下方日志区域了解处理进度
                    4. 支持同时处理多个学生的数据文件

                    注意事项：
                    - 请确保Excel文件命名符合规范
                    - 处理过程中请勿操作鼠标键盘
                    - 生成时间取决于数据量大小"""

# 登录类
class LoginApp:
    def __init__(self, root, callback):
        self.root = root
        self.callback = callback  # 登录成功后的回调函数

        # 高DPI设置
        self.root.tk.call('tk', 'scaling', 1.5)

        # 设置窗口属性
        self.root.title("智慧课堂学情分析系统")
        self.root.geometry("320x280")
        self.root.resizable(False, False)
        self.root.configure(bg=StyleConfig.COLORS["light"])

        # 创建样式
        self.style = ttk.Style()
        self.style.theme_use('clam')
        self._configure_styles()

        # 主容器
        self.main_frame = ttk.Frame(self.root, padding=20)
        self.main_frame.pack(expand=True, fill="both")

        # 界面元素
        self._create_widgets()

        # 窗口居中
        self._center_window()

    def _configure_styles(self):
        self.style.configure('TFrame', background=StyleConfig.COLORS["light"])
        self.style.configure('TLabel',
                             font=("微软雅黑", 10),
                             background=StyleConfig.COLORS["light"])
        self.style.configure('TButton',
                             font=("微软雅黑", 10),
                             padding=6,
                             relief="flat",
                             background=StyleConfig.COLORS["primary"],
                             foreground="white")
        self.style.map('TButton',
                       background=[('active', StyleConfig.COLORS["dark"])])

    def _create_widgets(self):
        # 标题
        header = ttk.Label(self.main_frame,
                           text="智慧课堂学情分析系统",
                           font=("微软雅黑", 16, "bold"),
                           foreground=StyleConfig.COLORS["primary"],
                           anchor="center")
        header.grid(row=0, column=0, columnspan=2, pady=(0, 20))

        # 输入区域
        input_frame = ttk.Frame(self.main_frame)
        input_frame.grid(row=1, column=0, columnspan=2, sticky="ew")

        ttk.Label(input_frame, text="用户名:").grid(row=0, column=0, sticky="w", pady=5)
        self.username = ttk.Entry(input_frame)
        self.username.grid(row=0, column=1, sticky="ew", padx=5, pady=5)

        ttk.Label(input_frame, text="密  码:").grid(row=1, column=0, sticky="w", pady=5)
        self.password = ttk.Entry(input_frame, show="•")
        self.password.grid(row=1, column=1, sticky="ew", padx=5, pady=5)

        # 按钮区域
        btn_frame = ttk.Frame(self.main_frame)
        btn_frame.grid(row=2, column=0, columnspan=2, pady=15)

        self.login_btn = ttk.Button(btn_frame,
                                    text="登  录",
                                    command=self.on_login)
        self.login_btn.pack(side="top", fill="x", padx=20)

        # 状态栏
        self.status_label = ttk.Label(self.main_frame,
                                      text="",
                                      foreground=StyleConfig.COLORS["danger"],
                                      anchor="center")
        self.status_label.grid(row=3, column=0, columnspan=2, pady=(10, 0))

        # 配置网格列权重
        self.main_frame.columnconfigure(0, weight=1)
        input_frame.columnconfigure(1, weight=1)

        # 自动聚焦用户名输入框
        self.username.focus_set()

        # 绑定回车事件
        self.username.bind("<Return>", lambda e: self.on_login())
        self.password.bind("<Return>", lambda e: self.on_login())

    def _center_window(self):
        self.root.update_idletasks()
        width = self.root.winfo_width()
        height = self.root.winfo_height()
        x = (self.root.winfo_screenwidth() // 2) - (width // 2)
        y = (self.root.winfo_screenheight() // 2) - (height // 2)
        self.root.geometry(f'+{x}+{y}')

    def validate_inputs(self):
        """验证输入是否完整"""
        errors = []
        if not self.username.get():
            errors.append("用户名不能为空")
        if not self.password.get():
            errors.append("密码不能为空")

        if errors:
            self.status_label.config(
                text="\n".join(errors),
                foreground="red"
            )
            return False
        return True

    def on_login(self):
        """处理登录点击事件"""
        if not self.validate_inputs():
            return

        self.login_btn.config(state="disabled", text="登录中...")
        self.status_label.config(text="正在验证登录信息...", foreground="green")

        # 在独立线程中执行登录
        threading.Thread(
            target=self.login_thread,
            args=(self.username.get(), self.password.get()),
            daemon=True
        ).start()

    def login_thread(self, username, password):
        """执行登录逻辑"""
        try:
            client = MongoClient(Config.connection_string)
            db = client["RS"]  # 替换为你的数据库名称
            collection = db["user"]  # 替换为你的集合名称

            # # 插入数据
            # collection.insert_one({"name": "Alice", "age": 25})

            # 查询数据
            user = collection.find_one({"nm": username,"pwd": password})
            print(user)
            if user:
                # 登录成功后处理
                self.root.after(0, self.on_login_success)
            else:
                self.login_btn.config(state="normal", text="登  录")  # 恢复按钮状态
                self.root.after(0, self.on_login_fail,'用户名或密码错误，请重试')

        except Exception as e:
            error_msg = f"登录失败: {str(e)}"
            self.root.after(0, self.on_login_fail, error_msg)

    def on_login_success(self):
        """登录成功处理"""
        self.status_label.config(text="登录成功，正在进入系统...", foreground="green")
        self.root.after(1000, self.root.destroy)
        self.callback()  # 显示主界面

    def on_login_fail(self, error_msg):
        """登录失败处理"""
        self.login_btn.config(state="normal", text="登  录")  # 恢复按钮状态
        self.status_label.config(text=error_msg, foreground="red")  # 显示错误信息

# 表格修复类
class TableFixer:
    @classmethod
    def fix_excel_errors(cls, file_path, log_callback):
        try:
            log_callback(f"开始修复文件：{file_path}", "INFO")
            excel = win32.Dispatch('Excel.Application')
            excel.Visible = False  # 隐藏Excel界面
            excel.DisplayAlerts = False  # 禁用警告提示

            # 打开工作簿
            wb = excel.Workbooks.Open(file_path)
            ws = wb.Worksheets(1)

            # 清除可能存在的错误格式
            used_range = ws.UsedRange
            # used_range.TextToColumns()  # 自动格式化数据
            # used_range.ClearFormats()  # 清除格式

            # 清除前后空字符串
            for cell in used_range.Cells:
                if cell.Value is not None and isinstance(cell.Value, str):
                    cell.Value = cell.Value.strip()

            # 保存并关闭
            wb.Save()
            wb.Close()
            excel.Quit()

            # 确保释放COM对象
            del used_range, ws, wb, excel
            log_callback("文件修复成功", "SUCCESS")
            return True
        except Exception as e:
            log_callback(f"修复失败：{str(e)}", "ERROR")
            return False
        finally:
            # 确保进程退出
            os.system('taskkill /f /im EXCEL.EXE >nul 2>&1')

class EnhancedGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("OK 智慧课堂学情报告生成系统")

        # 设置窗口大小
        self.root.geometry(StyleConfig.DIMENSIONS["window"])

        # 获取屏幕宽度和高度
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()

        # 计算窗口居中的位置
        window_width, window_height = map(int, StyleConfig.DIMENSIONS["window"].split("x"))
        x = (screen_width - window_width) // 2
        y = (screen_height - window_height) // 2

        # 设置窗口位置
        self.root.geometry(f"{StyleConfig.DIMENSIONS['window']}+{x}+{y}")

        self.file_path = None
        self.processing = threading.Event()  # 使用 threading.Event 替代布尔值
        # 初始化样式
        self._init_styles()
        self._setup_ui()
        self.log_queue = queue.Queue()
        self.progress_queue = queue.Queue()  # 用于进度条更新的队列
        self._start_log_thread()
        self._start_progress_thread()
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.exit_system)

        self.success_count = 0
        self.failure_count = 0
        self.total_files = 0

    def _init_styles(self):
        style = ttk.Style()
        style.theme_use("clam")

        # 按钮样式
        style.configure("Custom.TButton",
                        background=StyleConfig.COLORS["primary"],
                        foreground=StyleConfig.COLORS["light"],
                        padding=(20, 10),  # 增加按钮的大小
                        font=StyleConfig.FONTS["button"]
                        )
        style.map("Custom.TButton",
                  foreground=[("active", StyleConfig.COLORS["light"]),
                              ("!active", StyleConfig.COLORS["light"])],
                  background=[("active", StyleConfig.COLORS["dark"]),
                              ("!active", StyleConfig.COLORS["primary"])])

        # Treeview样式
        style.configure("Treeview.Heading",
                        font=(StyleConfig.FONTS["treeview"][0], StyleConfig.FONTS["treeview"][1], "bold"),
                        background=StyleConfig.COLORS["primary"],
                        foreground=StyleConfig.COLORS["light"])
        style.configure("Treeview",
                        font=StyleConfig.FONTS["treeview"],
                        fieldbackground=StyleConfig.COLORS["light"],
                        rowheight=25)

    def _setup_ui(self):
        self._create_header()
        self._create_control_panel()
        self._create_status_panel()
        self._create_footer()  # 添加版权信息

    def _create_header(self):
        header = tk.Frame(self.root, bg=StyleConfig.COLORS["primary"], height=50)
        header.pack(fill="x", pady=(0, 20))

        tk.Label(header,
                 text="OK学情报告系统",
                 font=StyleConfig.FONTS["title"],
                 fg=StyleConfig.COLORS["light"],
                 bg=StyleConfig.COLORS["primary"]).pack(pady=20)

    def _create_control_panel(self):
        # 左侧控制面板
        control_frame = tk.Frame(self.root, bg=StyleConfig.COLORS["light"], width=220)
        control_frame.pack(side="left", fill="y", padx=20, pady=20)

        # 按钮垂直排列
        buttons = [
            {"text": "📁 选择路径", "command": self.select_path},
            {"text": "📊 学情报告", "command": self.generate_report},
            {"text": "📊 教师月报", "command": self.generate_teacher_report},
            {"text": "🔍 查看结果", "command": self.open_result_folder},  # 新增按钮
            {"text": "❓ 帮助信息", "command": self.show_help},
            {"text": "❌ 退出系统", "command": self.exit_system},
            {"text": "🧹 清空日志", "command": self.clear_log}  # 增加清空按钮
        ]

        for btn in buttons:
            ttk.Button(control_frame,
                       text=btn["text"],
                       style="Custom.TButton",
                       command=btn["command"]).pack(fill="x", pady=12, padx=10)

    def _create_status_panel(self):
        status_frame = tk.Frame(self.root, bg=StyleConfig.COLORS["light"])
        status_frame.pack(fill="both", expand=True, padx=20, pady=10)

        # 数据标签区域
        data_frame = tk.Frame(status_frame, bg=StyleConfig.COLORS["light"])
        data_frame.pack(fill="x", pady=5)

        # 待处理文件数
        ttk.Label(data_frame, text="待处理文件数:", background=StyleConfig.COLORS["light"]).pack(side="left", padx=10)
        self.pending_label = ttk.Label(data_frame, text="0", background=StyleConfig.COLORS["light"])
        self.pending_label.pack(side="left", padx=10)

        # 进度
        ttk.Label(data_frame, text="进度:", background=StyleConfig.COLORS["light"]).pack(side="left", padx=10)
        self.progress_label = ttk.Label(data_frame, text="0%", background=StyleConfig.COLORS["light"])
        self.progress_label.pack(side="left", padx=10)

        # 成功
        ttk.Label(data_frame, text="成功:", background=StyleConfig.COLORS["light"]).pack(side="left", padx=10)
        self.success_label = ttk.Label(data_frame, text="0", background=StyleConfig.COLORS["light"])
        self.success_label.pack(side="left", padx=10)

        # 失败
        ttk.Label(data_frame, text="失败:", background=StyleConfig.COLORS["light"]).pack(side="left", padx=10)
        self.failure_label = ttk.Label(data_frame, text="0", background=StyleConfig.COLORS["light"])
        self.failure_label.pack(side="left", padx=10)

        # 在状态面板中添加进度条
        self.progress_bar = ttk.Progressbar(status_frame, orient="horizontal", length=200, mode="determinate")
        self.progress_bar.pack(fill="x", pady=5)

        # 日志显示区域
        lbl_status = tk.Label(status_frame,
                            text="实时处理状态",
                            font=StyleConfig.FONTS["subtitle"],
                            anchor="w")
        lbl_status.pack(fill="x", pady=10)

        self.tree = ttk.Treeview(status_frame,
                               columns=("时间", "类型", "详情"),
                               show="headings",
                               height=StyleConfig.DIMENSIONS["treeview"]["height"])

        # 配置列
        self.tree.heading("时间", text="时间", anchor="w")
        self.tree.heading("类型", text="类型", anchor="w")
        self.tree.heading("详情", text="详情", anchor="w")

        self.tree.column("时间", width=60, anchor="w")
        self.tree.column("类型", width=40, anchor="w")
        self.tree.column("详情", width=600, anchor="w")

        self.tree.pack(fill="both", expand=True, pady=10)

    def _start_progress_thread(self):
        def progress_listener():
            while True:
                try:
                    progress = self.progress_queue.get(timeout=0.1)
                    self.root.after(0, self._update_progress, progress)
                except queue.Empty:
                    continue

        threading.Thread(target=progress_listener, daemon=True).start()

    def _update_progress(self, progress):
        self.progress_bar['value'] = progress
        self.progress_label.config(text=f"{progress}%")

    def _create_footer(self):
        footer = tk.Frame(self.root, bg=StyleConfig.COLORS["light"], height=50)
        footer.pack(fill="x", pady=20)

        copyright_label = ttk.Label(footer,
                                    text="© 2025 X.H.W. 版权所有.",
                                    font=("微软雅黑", 8),
                                    background=StyleConfig.COLORS["light"])
        copyright_label.pack(side="right", padx=10, pady=5)

    def update_stats(self, progress=None, success=None, failure=None):
        if progress is not None:
            self.progress_queue.put(progress)  # 将进度放入队列
            self.progress_label.config(text=f"{progress}%")
        if success is not None:
            self.success_count += success
            self.success_label.config(text=f"{self.success_count}")
        if failure is not None:
            self.failure_count += failure
            self.failure_label.config(text=f"{self.failure_count}")

    def select_path(self):
        if self.processing.is_set():  # 检查是否有其他任务正在运行
            self._log("程序运行中，请稍后重试！", "WARNING")
            return

        # 使用 filedialog 选择文件夹路径
        path = filedialog.askdirectory()
        if path:  # 如果用户选择了路径
            self.file_path = os.path.normpath(path)  # 规范化路径
            self._log(f"已选择工作目录：{self.file_path}", "INFO")
            self.root.title(f"OK 智慧课堂学情分析系统 - {self.file_path}")
        else:
            self._log("未选择任何路径", "WARNING")

    def generate_report(self):
        if not self.file_path:
            self._log("请先选择文件路径！", "WARNING")
            return
        if self.processing.is_set():
            self._log("程序运行中，请稍后重试！", "WARNING")
            return        (
        self.processing.set())  # 设置标志
        self._log("开始生成学情分析报告...", "INFO")
        threading.Thread(target=self._generate_report_thread, daemon=True).start()

    def _generate_report_thread(self):
        try:
            workflow = MainWorkflow(self,self._log)
            workflow.start(self.file_path)
            os.remove('demo.png')  # 删除临时图表文件
            self._log("报告生成完成！", "SUCCESS")
        except Exception as e:
            self._log(f"生成失败：{str(e)}", "ERROR")
        finally:
            self.processing.clear()  # 清除标志

    def generate_teacher_report(self):
        if not self.file_path:
            self._log("请先选择文件路径！", "WARNING")
            return
        if self.processing.is_set():
            self._log("程序运行中，请稍后重试！", "WARNING")
            return
        self.processing.set()
        self._log("开始生成教师教学分析报告...", "INFO")
        threading.Thread(target=self._generate_teacher_report_thread, daemon=True).start()

    def _generate_teacher_report_thread(self):
        try:
            workflow = TeacherWorkflow(self, self._log)
            workflow.start(self.file_path)
            os.remove('demo.png')  # 删除临时图表文件
            self._log("教师报告生成完成！", "SUCCESS")
        except Exception as e:
            self._log(f"生成失败：{str(e)}", "ERROR")
        finally:
            self.processing.clear()

    def open_result_folder(self):
        if self.file_path:
            try:
                os.startfile(self.file_path)
                self._log("已打开结果文件夹", "SUCCESS")
            except Exception as e:
                self._log(f"打开文件夹失败：{str(e)}", "ERROR")
        else:
            self._log("请先选择路径！", "WARNING")

    def show_help(self):
        messagebox.showinfo("系统帮助", Config.help_content)

    def exit_system(self):
        if messagebox.askyesno("退出程序", "确定要退出程序吗？"):
            self.root.destroy()
            sys.exit(0)

    def clear_log(self):
        for item in self.tree.get_children():
            self.tree.delete(item)

    def _start_log_thread(self):
        def log_listener():
            while True:
                try:
                    msg, level = self.log_queue.get(timeout=0.1)
                    self.root.after(0, self._update_log_ui, msg, level)
                except queue.Empty:
                    continue

        threading.Thread(target=log_listener, daemon=True).start()

    def _log(self, message, level="INFO"):
        self.log_queue.put((message, level))

    def _update_log_ui(self, message, level):
        timestamp = time.strftime("%H:%M:%S")
        self.tree.insert("", "end",
                         values=(timestamp, level, message),
                         tags=(level.lower(),))
        # 自动滚动控制
        self.tree.see(self.tree.get_children()[-1])

        # 颜色配置：根据日志级别设置颜色
        for log_level, color in StyleConfig.LOG_LEVELS.items():
            self.tree.tag_configure(log_level.lower(), foreground=color)

    def on_tree_item_click(self, event):
        item_id = self.tree.selection()[0]
        item = self.tree.item(item_id)
        messagebox.showinfo("详细信息", f"类别: {item['values'][0]}\n详细信息: {item['values'][1]}")


# 文件路径处理类
class FilePathHandler:
    @staticmethod
    def process_files(folder_path, log_callback):
        if not os.path.exists(folder_path) or not os.path.isdir(folder_path):
            log_callback(f"路径无效或不存在：{folder_path}", "ERROR")
            return []
        files = os.listdir(folder_path)
        log_callback(f"发现文件数：{len(files)}", "INFO")

        excel_files = []
        for file in files:
            _, ext = os.path.splitext(file)
            if ext in ('.xls', '.xlsx'):
                excel_files.append(os.path.join(folder_path, file))
        log_callback(f"找到 Excel 文件数：{len(excel_files)}", "INFO")
        return excel_files


# 数据处理类
class DataProcessor:
    def __init__(self, file_path, log_callback):
        self.file_path = file_path
        self.log = log_callback
        self.df = None
        self.load_data()

    def load_data(self):
        try:
            # 新增修复步骤
            if not TableFixer.fix_excel_errors(self.file_path, self.log):
                self.log(f"文件修复失败，跳过处理：{self.file_path}", "ERROR")
                return
            self.df = pd.read_excel(self.file_path)  # 读取 Excel 文件
            self.log(f"成功加载文件：{os.path.basename(self.file_path)}", "SUCCESS")
        except Exception as e:
            self.log(f"文件加载失败：{str(e)}", "ERROR")
            raise

    def get_student_info(self):
        try:
            filename = os.path.basename(self.file_path)
            if '-' not in filename:
                raise ValueError("文件名格式错误，缺少分隔符'-'")

            name_part = filename.split('-')[0]
            date_match = re.search(r'\((.*?)\)', filename)
            if not date_match:
                raise ValueError("文件名中未找到日期信息")

            date_str = date_match.group(1)
            if len(date_str) != 6:
                raise ValueError("日期格式不正确，应为6位数字")

            grade = os.path.basename(os.path.dirname(self.file_path))
            return name_part, f"20{date_str[0:2]}-{date_str[2:4]}-{date_str[4:6]}", grade

        except Exception as e:
            self.log(f"学生信息解析失败：{str(e)}", "ERROR")
            raise

    @classmethod
    def count_each_sub(cls, data, status_list, col_title='知识点状态'):
        each_list = []
        for m in status_list:
            if np.isin(m, data[col_title]):  # 判断状态是否存在
                each_list.append(int(data[col_title].value_counts()[m]))  # 统计数量
            else:
                each_list.append(0)
        return each_list

    def get_teacher_info(self):
        # 提取教师信息逻辑
        pass

    def aggregate_class_data(self):
        # 按班级聚合数据
        pass

    def aggregate_subject_data(self):
        # 按学科聚合数据
        pass

# 图表生成器
class ChartGenerator:
    @staticmethod
    def generate_all_sub_hist(data, subjects, states, colors):
        plt.rcParams['font.sans-serif'] = 'Microsoft YaHei'  # 设置中文字体
        fig, ax = plt.subplots(figsize=(6.5, 2.5))  # 创建图形
        data_h2v = data.swapaxes(1, 0)  # 数据行列转换
        left = None
        for i in range(len(data_h2v)):
            if i == 0:
                left = data_h2v[i]  # 第一个状态
                pic = ax.barh(subjects, data_h2v[i], color=colors[i], label=states[i])  # 绘制条形图
            else:
                pic = ax.barh(subjects, data_h2v[i], left=left, color=colors[i], label=states[i])  # 绘制堆积条形图
                left = left + data_h2v[i]

            all_labels = ax.bar_label(pic, label_type="center")  # 添加标签
            for lab in all_labels:
                if lab.get_text() == '0':  # 去除 0 的标签
                    lab.set_text('')

        plt.title('各学科知识点状态统计图')  # 图表标题
        plt.legend(title='知识点六层级', bbox_to_anchor=(1.01, 1), loc='upper left')  # 图例
        plt.tight_layout()  # 自动调整布局
        plt.savefig('demo.png')  # 保存图表
        plt.close()

    @staticmethod
    def generate_single_sub_pie(data, states, colors):
        plt.figure(figsize=(3, 2))  # 创建图形
        total = np.sum(data)  # 总知识点数量
        temp_sta = [sta for sta, count in zip(states, data) if count > 0]  # 非零状态
        temp_data = [count for count in data if count > 0]  # 非零数量
        temp_color = [color for color, count in zip(colors, data) if count > 0]  # 非零颜色

        explode = []
        for val in temp_data:
            explode.append(0.2 if val / total < 0.05 else 0)  # 设置爆炸效果

        plt.pie(temp_data, labels=temp_sta, labeldistance=1.3, autopct=format_zero,  # 绘制饼图
                colors=temp_color, pctdistance=0.8, wedgeprops={'width': 0.4},
                explode=explode,
                textprops={'rotation_mode': 'anchor', 'fontsize': 7,
                           'ha': 'center', 'va': 'center', 'rotation': 0})
        plt.axis('equal')  # 设置图形为圆形
        plt.savefig('demo.png')  # 保存图表
        plt.close()

    @staticmethod
    def generate_teacher_subject_chart(data, subjects, colors):
        plt.figure(figsize=(8, 5))
        plt.barh(subjects, data, color=colors)
        plt.title('教师所教学科知识点掌握率')
        plt.xlabel('掌握率 (%)')
        plt.tight_layout()
        plt.savefig('demo.png')
        plt.close()

# 报告生成器
class ReportGenerator:
    def __init__(self, document):
        self.document = document  # Word 文档对象

    def set_content_style(self, doc_text, **kwargs):
        content = self.document.add_paragraph()  # 添加段落
        content.alignment = kwargs.get('alignment', WD_ALIGN_PARAGRAPH.LEFT)  # 对齐方式
        content.paragraph_format.space_before = kwargs.get('space_before', Pt(0))  # 段前间距
        content.paragraph_format.space_after = kwargs.get('space_after', Pt(0))  # 段后间距
        content.paragraph_format.left_indent = kwargs.get('left_indent', Pt(0))  # 左缩进
        content.paragraph_format.first_line_indent = kwargs.get('first_line_indent', Pt(0))  # 首行缩进
        run = content.add_run(doc_text)  # 添加文本运行
        run.font.size = kwargs.get('font_size', Pt(9))  # 字体大小
        run.font.name = kwargs.get('en_name', 'Times New Roman')  # 字体名称
        run.element.rPr.rFonts.set(qn('w:eastAsia'), kwargs.get('cn_name', '等线'))  # 中文字体
        run.font.bold = kwargs.get('bold', False)  # 是否加粗
        run.font.color.rgb = kwargs.get('color', RGBColor(0, 0, 0))  # 字体颜色

    def add_picture(self, pic_path):
        self.document.add_picture(pic_path)  # 添加图片


# 主工作流
class MainWorkflow:
    def __init__(self, gui, log_callback=None):
        self.gui = gui  # 添加这一行来存储 gui 实例
        self._log = log_callback
        self.folder_path = None  # 文件夹路径
        self.excel_files = []  # Excel 文件列表
        self.current_file = None  # 当前文件路径
        self.doc = None  # Word 文档对象

    def start(self, file_path):
        excel_files = FilePathHandler.process_files(file_path, self._log)
        self.gui.total_files = len(excel_files)
        self.gui.pending_label.config(text=f"{self.gui.total_files}")

        success_count = 0
        failure_count = 0

        for idx, file in enumerate(excel_files):
            try:
                self.current_file = file
                self.process_file()
                success_count += 1
                self.gui.update_stats(success=1)
            except Exception as e:
                failure_count += 1
                self.gui.update_stats(failure=1)
                self._log(f"文件处理失败：{str(e)}", "ERROR")

            # 更新进度
            progress = int((idx + 1) / self.gui.total_files * 100)
            self.gui.update_stats(progress=progress)
            self.gui.pending_label.config(
                text=f"{self.gui.total_files - (idx + 1)}")

        # 最终状态更新
        self.gui.progress_label.config(text="进度: 100%")
        self.gui._log(f"处理完成！成功: {success_count}, 失败: {failure_count}", "SUCCESS")

    def process_file(self):
        try:
            if not self.current_file:
                self._log("当前文件路径为空，跳过处理", "WARNING")
                return
            data_processor = DataProcessor(self.current_file, self._log)  # 数据处理器
            # print(data_processor)
            self._log(f"开始获取学生信息，当前处理的文件：{self.current_file}", "INFO")
            name, end_time, grade = data_processor.get_student_info()  # 获取学生信息
            self._log(f"获取到学生信息：{grade}-{name}", "INFO")

            self.doc = Document()  # 创建 Word 文档
            self.setup_document_layout()  # 设置文档布局
            self.generate_report_content(data_processor.df, name, grade, end_time)  # 生成报告内容
            save_folder_path = self.current_file[:self.current_file.rfind('\\')] + "学情报告"  # 保存路径
            if not os.path.exists(save_folder_path):
                os.makedirs(save_folder_path)
                self._log(f"文件夹已创建：{save_folder_path}","INFO")
            else:
                self._log(f"文件夹已存在：{save_folder_path}","WARNING")
            save_path = save_folder_path + f"\\{grade + name}.docx"
            self.doc.save(save_path)  # 保存文档
            self._log(f"报告已保存到：{save_path}", "SUCCESS")
        except Exception as e:
            self._log(f"文件处理失败：{str(e)}", "ERROR")
            raise

    def setup_document_layout(self):
        section = self.doc.sections[0]  # 获取文档页面布局
        section.left_margin = Cm(1.3)  # 设置左边距
        section.right_margin = Cm(1.3)  # 设置右边距
        section.top_margin = Cm(1.3)  # 设置上边距
        section.bottom_margin = Cm(1.3)  # 设置下边距

    def generate_report_content(self, df, name, grade, end_time):
        report_generator = ReportGenerator(self.doc)  # 报告生成器
        self.generate_fixed_content(report_generator, name, grade, end_time)  # 生成固定内容
        all_data = self.process_data(df)  # 处理数据
        self.generate_charts(all_data)  # 生成图表
        # 知识点六层级介绍
        for content in Config.tips_content:
            ReportGenerator(self.doc).set_content_style(doc_text=content,
                          font_size=Pt(7), bold=True, left_indent=Pt(50), )
        self.generate_sub_chart_details(df, all_data)  # 生成学科详情

    def generate_fixed_content(self, report_generator, name, grade, end_time):
        labels = Config.LABELS  # 固定内容标签
        all_doc_text = {
            'title': f'{name}{labels["title"]}',
            'para1': labels["para1"].format(grade=grade, student=name),
            'para2': labels["para2"],
            'para3': labels["para3"],
            'para4': labels["para4"].format(end_time=end_time),
            'para5': labels["para5"],
            'para6': labels["para6"]
        }  # 报告内容

        report_generator.set_content_style(
            doc_text=all_doc_text['title'],
            font_size=Pt(26),
            bold=True,
            alignment=WD_ALIGN_PARAGRAPH.CENTER,
            space_before=Pt(3),
            space_after=Pt(5)
        )  # 设置报告标题样式

        for idx, key in enumerate(['para1', 'para2', 'para3', 'para4', 'para5', 'para6']):
            report_generator.set_content_style(
                doc_text=all_doc_text[key],
                font_size=Pt(9),
                first_line_indent=Pt(18) if idx < 2 else Pt(0),
                bold=True if idx == 5 else False,
                color=RGBColor(255, 0, 0) if idx == 3 else RGBColor(0, 0, 0)
            )  # 设置段落内容样式

    def process_data(self, df):
        all_data = []
        for sub in Config.SUBJECTS:
            tempdata = df[df['学科名称'] == sub]  # 筛选学科数据
            all_data.append(DataProcessor.count_each_sub(tempdata, Config.STATES))  # 统计知识点状态
        return np.array(all_data)

    def generate_charts(self, all_data):
        ChartGenerator.generate_all_sub_hist(all_data, Config.SUBJECTS, Config.STATES, Config.COLORS)  # 绘制堆积条形图
        self.doc.add_picture('demo.png')  # 添加图表到文档        # 新增居中代码
        last_paragraph = self.doc.paragraphs[-1]
        last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

    def generate_sub_chart_details(self, df, all_data):
        report_generator = ReportGenerator(self.doc)
        for i in range(len(Config.SUBJECTS)):
            temp_subdata = all_data[i]
            total = np.sum(temp_subdata)
            if total == 0:
                continue
            report_generator.set_content_style(
                doc_text=f'{Config.SUBJECTS[i]}知识点掌握情况({total})',
                font_size=Pt(9),
                space_before=Pt(5),
                bold=True,
                color=RGBColor(0, 0, 102)
            )  # 设置学科标题样式
            ChartGenerator.generate_single_sub_pie(
                temp_subdata, Config.STATES, Config.COLORS
            )  # 绘制单学科饼图
            self.doc.add_picture('demo.png')  # 添加图表到文档
            # 新增居中代码
            last_paragraph = self.doc.paragraphs[-1]
            last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
            # 知识点详情
            for j in Config.STATES:
                self.to_k_content(report_generator, df.values, Config.SUBJECTS[i], j)

    @staticmethod
    def to_k_content(report_generator, data, sub, status):
        status_count = 0
        k_list = []
        for row in data:
            if row[2] == sub and row[3] == status:  # 假设学科在第3列，状态在第4列
                status_count += 1
                k_list.append("◉ " + row[1])  # 给知识点添加项目符号

        if status_count == 0:
            return

        # 添加状态标题
        report_generator.set_content_style(
            doc_text=f'{status}的知识点({status_count})',
            font_size=Pt(9),
            space_before=Pt(5),
            color=RGBColor(255, 0, 0),
            bold=True
        )

        # 创建5列表格,按行填充知识点（分栏）
        cols = 5
        rows = (len(k_list) + cols - 1) // cols  # 行数计算，向上取整
        table = report_generator.document.add_table(rows=rows, cols=cols)
        table.autofit = False  # 关闭自动调整

        # 设置表格样式
        table.style = "Table Grid"
        for cell in table.columns[0].cells:
            cell.width = Cm(3)  # 每栏3厘米宽（根据页面调整）

        # 将知识点按行填充到表格中
        for row_idx in range(rows):
            for col_idx in range(cols):
                index = row_idx * cols + col_idx
                if index < len(k_list):
                    cell = table.cell(row_idx, col_idx)
                    # 清空原有内容并设置新样式
                    p = cell.paragraphs[0]
                    p.clear()
                    # 设置段落对齐
                    p.alignment = WD_ALIGN_PARAGRAPH.LEFT
                    p.paragraph_format.space_before = Pt(2)
                    p.paragraph_format.space_after = Pt(2)
                    p.paragraph_format.left_indent = Pt(0)
                    p.paragraph_format.first_line_indent = Pt(0)
                    run = p.add_run(k_list[index])

                    # 设置字体属性
                    run.font.size = Pt(8)
                    run.font.name = '楷体'
                    run._element.rPr.rFonts.set(qn('w:eastAsia'), '楷体')

                    # 设置单元格垂直对齐
                    cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER

        # 设置表格样式
        for row in table.rows:
            for col_idx, cell in enumerate(row.cells):
                # 隐藏所有边框
                borders = {
                    'top': {'val': 'nil'},
                    'left': {'val': 'nil'},
                    'bottom': {'val': 'nil'},
                    'right': {'val': 'nil'}
                }

                # 仅在列之间添加虚线
                if col_idx < cols - 1:
                    borders['right'] = {'val': 'dashed', 'sz': '4', 'color': '888888'}
                set_cell_border(cell, **borders)


# 新增 TeacherWorkflow 类
class TeacherWorkflow:
    def __init__(self, gui, log_callback=None):
        self.gui = gui
        self._log = log_callback
        self.folder_path = None
        self.excel_files = []
        self.current_file = None
        self.doc = None

    def start(self, file_path):
        excel_files = FilePathHandler.process_files(file_path, self._log)
        self.gui.total_files = len(excel_files)
        self.gui.pending_label.config(text=f"{self.gui.total_files}")

        success_count = 0
        failure_count = 0

        for idx, file in enumerate(excel_files):
            try:
                self.current_file = file
                self.process_teacher_file()
                success_count += 1
                self.gui.update_stats(success=1)
            except Exception as e:
                failure_count += 1
                self.gui.update_stats(failure=1)
                self._log(f"文件处理失败：{str(e)}", "ERROR")

            progress = int((idx + 1) / self.gui.total_files * 100)
            self.gui.update_stats(progress=progress)
            self.gui.pending_label.config(text=f"{self.gui.total_files - (idx + 1)}")

        self.gui.progress_label.config(text="进度: 100%")
        self._log(f"处理完成！成功: {success_count}, 失败: {failure_count}", "SUCCESS")

    def process_teacher_file(self):
        try:
            if not self.current_file:
                self._log("当前文件路径为空，跳过处理", "WARNING")
                return
            data_processor = DataProcessor(self.current_file, self._log)
            teacher_name = data_processor.get_teacher_info()
            self.doc = Document()
            self.setup_document_layout()
            self.generate_report_content(data_processor.df, teacher_name)
            save_path = self.current_file[:self.current_file.rfind('\\')] + f"\\教师报告_{teacher_name}.docx"
            self.doc.save(save_path)
            self._log(f"报告已保存到：{save_path}", "SUCCESS")
        except Exception as e:
            self._log(f"文件处理失败：{str(e)}", "ERROR")
            raise

    def setup_document_layout(self):
        section = self.doc.sections[0]
        section.left_margin = Cm(1.3)
        section.right_margin = Cm(1.3)
        section.top_margin = Cm(1.3)
        section.bottom_margin = Cm(1.3)

    def generate_report_content(self, df, teacher_name):
        report_generator = TeacherReportGenerator(self.doc)
        class_data = DataProcessor.aggregate_class_data()
        subject_data = DataProcessor.aggregate_subject_data()

        report_generator.generate_class_analysis(class_data)
        report_generator.generate_subject_comparison(subject_data)


# 新增 TeacherReportGenerator 类
class TeacherReportGenerator:
    def __init__(self, document):
        self.document = document

    def generate_class_analysis(self, class_data):
        self.document.add_heading('班级学情分析', level=1)
        table = self.document.add_table(rows=1, cols=3)
        hdr_cells = table.rows[0].cells
        hdr_cells[0].text = '班级'
        hdr_cells[1].text = '平均掌握率'
        hdr_cells[2].text = '知识点总数'

        for cls, rate, total in class_data:
            row_cells = table.add_row().cells
            row_cells[0].text = cls
            row_cells[1].text = f'{rate:.1f}%'
            row_cells[2].text = str(total)

    def generate_subject_comparison(self, subject_data):
        self.document.add_heading('学科对比分析', level=1)
        self.document.add_paragraph('各学科知识点掌握情况对比：')

        # 生成图表
        ChartGenerator.generate_teacher_subject_chart(subject_data, Config.SUBJECTS, Config.COLORS)
        self.document.add_picture('demo.png')
        last_paragraph = self.document.paragraphs[-1]
        last_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER

def format_zero(val):
    return f'{val:.1f}%' if val != 0 else ''  # 格式化数值，0 不显示


def set_cell_border(cell, **kwargs):
    tc = cell._tc
    tcPr = tc.get_or_add_tcPr()
    tcBorders = tcPr.first_child_found_in("w:tcBorders")
    if tcBorders is None:
        tcBorders = OxmlElement('w:tcBorders')
        tcPr.append(tcBorders)

    for edge in ('top', 'left', 'bottom', 'right'):
        edge_data = kwargs.get(edge)
        if edge_data:
            element = OxmlElement(f'w:{edge}')
            for key, value in edge_data.items():
                element.set(qn(f'w:{key}'), str(value))
            tcBorders.append(element)

# 主程序入口
def main():
    def show_main_ui():
        app = EnhancedGUI()
        app.root.mainloop()

    # 显示登录窗口
    login_root = tk.Tk()
    login_root.withdraw()  # 隐藏主窗口
    login_window = tk.Toplevel(login_root)
    LoginApp(login_window, show_main_ui)
    login_root.mainloop()


if __name__ == "__main__":
    main()